<?php
/**
 *+------------------
 * madong
 *+------------------
 * Copyright (c) https://gitee.com/motion-code  All rights reserved.
 *+------------------
 * Author: Mr. April (405784684@qq.com)
 *+------------------
 * Official Website: http://www.madong.tech
 */

namespace madong\think\wf\services;

use madong\think\wf\basic\BaseService;
use madong\think\wf\dao\ProcessDefineDao;
use madong\ingenious\enums\ProcessDefineStateEnum;
use madong\ingenious\ex\LFlowException;
use madong\ingenious\interface\model\IProcessDefine;
use madong\ingenious\interface\services\IProcessDefineService;
use madong\ingenious\libs\utils\ArrayHelper;
use madong\ingenious\libs\utils\AssertHelper;
use madong\ingenious\libs\utils\Pagination;
use madong\ingenious\libs\utils\PropertyCopier;
use madong\ingenious\libs\utils\RedisCache;
use madong\ingenious\model\ProcessModel;
use madong\ingenious\parser\ModelParser;

class ProcessDefineService extends BaseService implements IProcessDefineService
{

    public function __construct()
    {
        $this->dao = new ProcessDefineDao();
    }

    public function created(object $param): ?IProcessDefine
    {
        unset($param->id);
        $model = $this->dao->getModel();
        PropertyCopier::copyProperties($param, $model);
        return $this->dao->save();
    }

    public function updated(object $param): bool
    {
        AssertHelper::notNull($param->id ?? '', '参数ID不能为空');
        $model = $this->dao->get($param->id);
        PropertyCopier::copyProperties($param, $model);
        return $model->save();
    }

    public function del(string|int|array $data): array
    {
        return $this->transaction(function () use ($data) {
            try {
                $data       = ArrayHelper::normalize($data);
                $deletedIds = [];
                foreach ($data as $id) {
                    $item = $this->dao->get($id);
                    if (!$item) {
                        continue; // 如果找不到项，跳过
                    }
                    $item->delete();
                    $primaryKey   = $item->getPk();
                    $deletedIds[] = $item->{$primaryKey};
                }
                return $deletedIds;
            } catch (\Throwable $e) {
                throw new LFlowException($e->getMessage());
            }
        });
    }

    public function list(object $param): array
    {
        $where = ArrayHelper::paramsFilter($param, [
            ['name', ''],
            ['display_name', ''],
            ['type_id', ''],
            ['enabled', ''],
            ['version', ''],
            ['is_active'],
        ]);
        [$page, $limit] = Pagination::getPageValue($param);
        $items = $this->dao->selectList($where, '*', $page, $limit, 'name,version', [], true);
        $total = $this->dao->count($where, true);
        return compact('items', 'total');
    }

    public function findById(string $id): ?IProcessDefine
    {
        AssertHelper::notNull($id, '参数process_define_id不能为空');
        $processDefine = $this->get($id);
        AssertHelper::notNull($processDefine, '流程定义不存在或被删除');
        if (!empty($processDefine->getData('content'))) {
            $graph_data = $processDefine->getData('content');
            //使用表单key获取表单JSON数据
            $processDefine->set('form', (object)[]);
            if (isset($graph_data->instance_url) && !empty($graph_data->instance_url)) {
                $processFormService = new ProcessFormService();
                $processForm        = $processFormService->findByName($graph_data->instance_url);
                if (!empty($processForm)) {
                    $latest_history = $processForm->getData('latest_history');
                    if (!empty($latest_history)) {
                        $processDefine->set('form', $latest_history->getData('content'));
                    }
                }
            }
        }
        return $processDefine;
    }

    public function deploy($param, string $operation): bool
    {
        try {
            // 1. json定义文件转成流程模型
//        $processModel = ModelParser::parse($param);
            // 2. 根据名称查询，取最新版本的流程定义记录
            $map1                 = [
                'name' => $param->name,
            ];
            $processDesignService = new ProcessDesignService();
            $processDesignData    = $processDesignService->dao->get($map1, ['*']);
            AssertHelper::notNull($processDesignData, '请先定义流程');
            $model   = $this->dao->get($map1, ['*'], [], 'version desc');
            $version = 1.0;
            if ($model !== null) {
                $this->dao->update(['name' => $param->name, 'is_active' => 1], ['is_active' => 0]);
                $version = $model->version + 0.1;
            }
            $attributes = [
                'name'         => $param->name ?? '',
                'display_name' => $param->display_name ?? '',
                'type_id'      => $processDesignData->getData('type_id'),
                'icon'         => $processDesignData->getData('icon'),
                'description'  => $processDesignData->getData('description'),
                'create_time'  => time(),
                'update_time'  => time(),
                'create_user'  => $operation,
                'enabled'      => ProcessDefineStateEnum::ENABLE->value,
                'is_active'    => 1,
                'version'      => $version,
                'content'      => $param,
            ];
            $newModel   = $this->dao->getModel();
            PropertyCopier::copyProperties((object)$attributes, $newModel);
            return $newModel->save();
        } catch (\Throwable $e) {
            throw new LFlowException($e->getMessage());
        }
    }

    /**
     * @throws \ReflectionException
     */
    public function redeploy(string|int $processDefineId, object $inputStream, string|int $operation): bool
    {
        try {
//             $processModel = ModelParser::parse($inputStream);
            $model = $this->dao->get($processDefineId);
            AssertHelper::notNull($model, '该流程未部署,请先部署流程');
            $map1                 = [
                'name' => $inputStream->name,
            ];
            $processDesignService = new ProcessDesignService();
            $processDesignData    = $processDesignService->dao->get($map1, ['*']);
            AssertHelper::notNull($processDesignData, '请先定义流程');
            $attributes = [
                'name'         => $processDesignData->getData('name'),
                'display_name' => $processDesignData->getData('display_name'),
                'type_id'      => $processDesignData->getData('type_id'),
                'icon'         => $processDesignData->getData('icon'),
                'description'  => $processDesignData->getData('description'),
                'update_time'  => time(),
                'update_user'  => $operation,
                'enabled'      => ProcessDefineStateEnum::ENABLE->value,
                'is_active'    => 1,
                'content'      => $inputStream,
            ];
            PropertyCopier::copyProperties((object)$attributes, $model);
            return $model->save();
        } catch (\Throwable $e) {
            throw new LFlowException($e->getMessage());
        }
    }

    public function enable(string|int|array $data, string|int $operation): array
    {
        return $this->transaction(function () use ($data, $operation) {
            try {
                $updIds = [];
                $data   = ArrayHelper::normalize($data);
                foreach ($data as $id) {
                    $model = $this->dao->get($id);
                    if (empty($model)) {
                        continue;
                    }
                    $model->set('enabled', ProcessDefineStateEnum::ENABLE->value);
                    $model->set('update_user', $operation);
                    $updIds[] = $id;
                    $model->save();
                }
                return $updIds;
            } catch (\Throwable $e) {
                throw new LFlowException($e->getMessage());
            }
        });
    }

    public function disable(string|int|array $data, string|int $operation): array
    {
        return $this->transaction(function () use ($data, $operation) {
            try {
                $updIds = [];
                $data   = ArrayHelper::normalize($data);
                foreach ($data as $id) {
                    $model = $this->dao->get($id);
                    if (empty($model)) {
                        continue;
                    }
                    $model->set('enabled', ProcessDefineStateEnum::DISABLE->value);
                    $model->set('update_user', $operation);
                    $model->save();
                    $updIds[] = $id;
                }
                return $updIds;
            } catch (\Throwable $e) {
                throw new LFlowException($e->getMessage());
            }
        });
    }

    public function getById(string $processDefineId): ?IProcessDefine
    {
        AssertHelper::notEmpty($processDefineId, '参数 processDefineId 不能为空');
        return $this->dao->get($processDefineId);
    }

    public function updateState(string $processDefineId, string|int $state, string|int $operation): void
    {
        AssertHelper::notEmpty($processDefineId, '参数 processDefineId 不能为空');
        $model = $this->dao->get($processDefineId);
        AssertHelper::notNull($processDefineId, '流程定义不存在或被删除');
        $model->enabled = $state;
        $model->save();
    }

    public function getProcessModel(string $processDefineId): ?ProcessModel
    {
        AssertHelper::notEmpty($processDefineId, '参数 processDefineId 不能为空');
        //如果缓存有则使用缓存
        return RedisCache::getCached(md5($processDefineId), null, 3600, function () use ($processDefineId) {
            return $this->processDefineToModel($this->dao->get($processDefineId));
        });
    }

    /**
     * @throws \ReflectionException
     */
    public function processDefineToModel(IProcessDefine $processDefine): ?ProcessModel
    {
        if (empty($processDefine)) return null;
        $content = $processDefine->getData('content');
        if (empty($content)) return null;
        return ModelParser::parse($content);
    }

    public function getDefineJsonStr(string $processDefineId): string|null
    {
        AssertHelper::notEmpty($processDefineId, '参数ID不能为空');
        $model = $this->dao->get($processDefineId);
        if (empty($model)) {
            return json_encode((object)[]);
        }
        $content = $model->getData('content');
        return json_encode(ArrayHelper::arrayToObject($content));
    }

    public function getDefineJsonObject(string $processDefineId): \stdClass|string|bool
    {
        AssertHelper::notEmpty($processDefineId, '参数ID不能为空');
        $model = $this->dao->get($processDefineId);
        if (empty($model)) {
            return json_encode((object)[]);
        }
        $content = $model->getData('content') ?? (object)[];
        return ArrayHelper::arrayToObject($content);
    }

    public function getLastByName(string $name): ?IProcessDefine
    {
        return $this->dao->selectList(['name' => $name], '*', 0, 0, 'version', [], true)->last();
    }

    public function getProcessDefineByVersion(string $name, int|float $version): ?IProcessDefine
    {
        return $this->selectList(['name' => $name, 'version' => $version], '*', 0, 0, '', [], true)->last();
    }

}
